﻿/* Copyright (c) 2009, 2010, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    partial class Assembler
    {
        private bool enc_intcall(InstructionLine i, int p, Metadata.MethodDefRow mdr, Signature.BaseMethod msig, Metadata.TypeDefRow tdr, Signature.Param tsig, AssemblerState state)
        {
            string methname;
            bool handled = true;

            Assembler.TypeToCompile ttc = new TypeToCompile { tsig = tsig, type = tdr };

            methname = Mangler.Mangle(tdr, tsig, mdr, msig, this);
            methname = methname.Substring(Mangler.Mangle(tdr.m).Length);

            if (methname == "_N6_System_T6_Object_S2_1C_M19_InternalGetHashCode_S8_0001081C")
            {
                var obj_var = i.stack_before[i.stack_before.Count - 1].contains_variable;
                if (obj_var.type != var.var_type.LogicalVar)
                {
                    i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, state.next_variable++, obj_var, var.Null));
                    obj_var = state.next_variable - 1;
                }
                var obj_id_var = state.next_variable++;

                int fld_offset = new Layout(ttc, this).GetField("Int32 __object_id", false).Offset;
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i4, obj_id_var, var.ContentsOf(obj_var, fld_offset), var.Null));
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.mul_i4, state.next_variable++, obj_id_var,
                    var.Const((UInt32)0x1E3779B1)));
            }
            else if (methname == "_N6_System_T6_Object_S2_1C_M7_GetType_S10_2000128128")
            {
                var obj_var = i.stack_before[i.stack_before.Count - 1].contains_variable;
                if (obj_var.type != var.var_type.LogicalVar)
                {
                    i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, state.next_variable++, obj_var, var.Null));
                    obj_var = state.next_variable - 1;
                }
                var vtbl_ptr_var = state.next_variable++;

                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, vtbl_ptr_var,
                    var.ContentsOf(obj_var), var.Null));
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, state.next_variable++,
                    var.ContentsOf(vtbl_ptr_var), var.Null));
            }
            else if (methname == "_N3_ABI_T16_MemoryOperations_Special_M6_PeekU1_S8_0001050B")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.peek_u1, state.next_variable++,
                    i.stack_before[i.stack_before.Count - 1].contains_variable, 0));
            }
            else if (methname == "_N3_ABI_T16_MemoryOperations_Special_M6_PeekU2_S8_0001070B")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.peek_u2, state.next_variable++,
                    i.stack_before[i.stack_before.Count - 1].contains_variable, 0));
            }
            else if (methname == "_N3_ABI_T16_MemoryOperations_Special_M4_Poke_S10_0002010B05")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.poke_u1, 0,
                    i.stack_before[i.stack_before.Count - 2].contains_variable,
                    i.stack_before[i.stack_before.Count - 1].contains_variable));
            }
            else if (methname == "_N3_ABI_T16_MemoryOperations_Special_M4_Poke_S10_0002010B07")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.poke_u2, 0,
                    i.stack_before[i.stack_before.Count - 2].contains_variable,
                    i.stack_before[i.stack_before.Count - 1].contains_variable));
            }
            else if (methname == "_N3_ABI_T16_MemoryOperations_Special_M4_Poke_S10_0002010B09")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.poke_u4, 0,
                    i.stack_before[i.stack_before.Count - 2].contains_variable,
                    i.stack_before[i.stack_before.Count - 1].contains_variable));
            }
            else if (methname == "_N3_ABI_T16_MemoryOperations_Special_M4_Poke_S10_0002010B0B")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.poke_u8, 0,
                    i.stack_before[i.stack_before.Count - 2].contains_variable,
                    i.stack_before[i.stack_before.Count - 1].contains_variable));
            }
            else if (methname == "_N3_ABI_T16_MemoryOperations_Special_M4_Poke_S10_0002010B19")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.poke_u, 0,
                    i.stack_before[i.stack_before.Count - 2].contains_variable,
                    i.stack_before[i.stack_before.Count - 1].contains_variable));
            }
            else if (methname == "_N3_ABI_T12_IoOperations_Special_M7_PortOut_S10_0002010705")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.portout_u2_u1, 0,
                    i.stack_before[i.stack_before.Count - 2].contains_variable,
                    i.stack_before[i.stack_before.Count - 1].contains_variable));
            }
            else if (methname.StartsWith("_N3_ABI_T14_CastOperations_Special_M18_ReinterpretAs"))
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, state.next_variable++,
                    i.stack_before[i.stack_before.Count - 1].contains_variable, 0));
            }
            else if (methname.StartsWith("_N3_ABI_T14_CastOperations_Special_M9_GetArg"))
            {
                char arg_c = methname["_N3_ABI_T14_CastOperations_Special_M9_GetArg".Length];
                var la = var.LocalArg(Int32.Parse(new String(new char[] { arg_c })));
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, state.next_variable++, la, var.Null));
            }
            else if (methname == "_N6_System_T6_String_S2_0E_M9_get_Chars_S8_20010308")
            {
                // char get_Chars(int32 idx)
                // if(idx >= length) throw IndexOutOfRangeException
                // if(idx < 0) throw IndexOutOfRangeException
                // addr = obj + obj.ClassSize + idx * char.Size
                // retval = peek_u2(addr)

                var obj_var_id = i.stack_before[i.stack_before.Count - 2].contains_variable;
                if (obj_var_id.type != var.var_type.LogicalVar)
                {
                    i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, state.next_variable++,
                        obj_var_id, var.Null));
                    obj_var_id = state.next_variable - 1;
                }
                var idx_var_id = i.stack_before[i.stack_before.Count - 1].contains_variable;

                // 1. length = *(obj_var_id + length_offset)
                Metadata.TypeDefRow ss_tdr = Metadata.GetTypeDef("mscorlib", "System", "String", this);
                Layout l = new Layout(new Assembler.TypeToCompile { type = ss_tdr, tsig = new Signature.Param(BaseType_Type.String) }, this);

                var length_var_id = state.next_variable++;
                int length_offset = l.GetField("Int32 length", false).Offset;
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i4, length_var_id,
                    var.ContentsOf(obj_var_id, length_offset), 0));

                // TODO Bounds checking

                // 2. idx_mul_charsize = idx * char_size
                var idx_mul_charsize_var_id = state.next_variable++;
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.mul_i, idx_mul_charsize_var_id,
                    idx_var_id, var.Const(2)));

                // 3. obj_class_size_add_idx_mul_charsize = obj.ClassSize + idx_mul_charsize
                var obj_class_size_add_idx_mul_charsize = state.next_variable++;
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.add_i, obj_class_size_add_idx_mul_charsize,
                    idx_mul_charsize_var_id, var.Const(l.ClassSize)));

                // 4. char_addr = obj + obj_class_size_add_idx_mul_charsize
                var char_addr = obj_class_size_add_idx_mul_charsize;
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.add_i, char_addr,
                    obj_class_size_add_idx_mul_charsize, obj_var_id));

                // 5. res = peek_u2(char_addr)
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.peek_u2, state.next_variable++,
                    char_addr, 0));
            }
            else if (methname == "_N6_System_T11_Environment_S6_1284DC_M11_get_NewLine_S6_00000E")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i4, state.next_variable++,
                    var.Const((ushort)0x000a), 0));
            }
            else if (methname == "_N18_System.Diagnostics_T10_StackTrace_S6_128780_M9_get_trace_S22_00031D12877C1281300802")
            {
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.assign_i, state.next_variable++,
                    var.Const(0L), var.Null));
            }
            else if (methname == "_N6_System_T6_String_S2_0E_M14_InternalStrcpy_S12_0003010E080E")
            {
                // this is static void InternalStrCpy(string a, int32 b, string c)
                // call void __ty_strcpy(string dest = a, int32 destPos = b, char []chars = null, string src = c, int32 sPos = -1, int32 count = -1)
                CallConv cc = MakeStaticCall("default", new Signature.Param(BaseType_Type.Void),
                        new List<Signature.Param> { new Signature.Param(BaseType_Type.String),
                            new Signature.Param(BaseType_Type.I4),
                            new Signature.Param(new Signature.ZeroBasedArray { ElemType = new Signature.BaseType(BaseType_Type.Char), numElems = 0 }, this),
                            new Signature.Param(BaseType_Type.String),
                            new Signature.Param(BaseType_Type.I4),
                            new Signature.Param(BaseType_Type.I4) },
                        ThreeAddressCode.Op.call_void);
                        
                i.tacs.Add(new CallEx(var.Null, new var[] { i.stack_before[i.stack_before.Count - 3].contains_variable,
                    i.stack_before[i.stack_before.Count - 2].contains_variable,
                    var.Const(new IntPtr(0)),
                    i.stack_before[i.stack_before.Count - 1].contains_variable,
                    var.Const((int)-1),
                    var.Const((int)-1) },
                    ThreeAddressCode.Op.call_void,
                    "__ty_strcpy",
                    cc));
            }
            else if (methname == "_N6_System_T6_Object_S2_1C_M7_GetType_S10_20001284F8")
            {
                // this is instance System.Type GetType()
                // call intptr __ty_gettype(intptr obj)
                i.tacs.Add(new CallEx(state.next_variable++, new var[] { i.stack_before[i.stack_before.Count - 1].contains_variable }, ThreeAddressCode.Op.call_i, "__ty_gettype",
                    MakeStaticCall("default", new Signature.Param(BaseType_Type.I), new List<Signature.Param> { new Signature.Param(BaseType_Type.I) }, ThreeAddressCode.Op.call_i)));
            }
            else if ((methname == "_N6_System_T11_Environment_S6_12843C_M11_get_NewLine_S6_00000E") ||
                (methname == "_N18_System.Diagnostics_T10_StackTrace_S6_128768_M9_get_trace_S22_00031D1287641282B80802") ||
                (methname == "_N6_System_T6_String_S2_0E_M19_InternalAllocateStr_S8_00010E08") ||
                (methname == "_N31_System.Runtime.CompilerServices_T14_RuntimeHelpers_S6_128D24_M22_get_OffsetToStringData_S6_000008") ||
                (methname == "_N6_System_T4_Type_S6_1284F8_M20_internal_from_handle_S12_00011284F818") ||
                (methname == "_N6_System_T6_String_S2_0E_M14_InternalStrcpy_S16_0005010E080E0808") ||
                (methname == "_N6_System_T6_String_S2_0E_M12_InternalJoin_S16_00040E0E1D0E0808") ||
                (methname == "_N6_System_T5_Array_S6_12831C_M13_GetLowerBound_S8_20010808") ||
                (methname == "_N6_System_T4_Type_S6_1284F8_M16_IsInstanceOfType_S8_2001021C") ||
                (methname == "_N6_System_T5_Array_S6_12831C_M9_GetLength_S8_20010808") ||
                (methname == "_N6_System_T5_Array_S6_12831C_M8_FastCopy_S24_00050212831C0812831C0808") ||
                (methname == "_N6_System_T9_ValueType_S4_1124_M19_InternalGetHashCode_S14_0002081C101D1C") ||
                (methname == "_N6_System_T5_Array_S6_12831C_M7_GetRank_S6_200008") ||
                (methname == "_N6_System_T5_Array_S6_12831C_M12_SetValueImpl_S10_2002011C08"))
            {
                // List methods that are not implemented directly by tysila, but should be handled by the system
                handled = false;
                return handled;
            }
            else if (methname == "_N6_System_T4_Math_S6_1284BC_M4_Sqrt_S8_00010D0D")
            {
                // static double Sqrt(double x)
                i.tacs.Add(new ThreeAddressCode(ThreeAddressCode.Op.sqrt_r8, state.next_variable++, i.stack_before[i.stack_before.Count - 1].contains_variable, var.Null));
            }
            else
            {
#if !DEBUG
                throw new NotImplementedException(methname);
#else
                handled = false;
                return handled;
#endif                
            }

            if (!(msig is Signature.Method))
            {
                throw new NotSupportedException();
            }

            Signature.Method msigm = msig as Signature.Method;
            i.pushes = msigm.RetType;
            if (i.pop_count < 0)
            {
                i.pop_count = msigm.Params.Count;
                if (msigm.HasThis && !msigm.ExplicitThis)
                    i.pop_count++;
            }
            if (msigm.RetType.CliType(this) != CliType.void_)
                i.pushes_variable = state.next_variable - 1;

            return handled;
        }
    }
}
